{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8b0e11f2-9ea4-48c2-b8d2-d0a4ba967827",
   "metadata": {},
   "source": [
    "# Gradio Day!\n",
    "\n",
    "Today we will build User Interfaces using the outrageously simple Gradio framework.\n",
    "\n",
    "Prepare for joy!\n",
    "\n",
    "Please note: your Gradio screens may appear in 'dark mode' or 'light mode' depending on your computer settings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c44c5494-950d-4d2f-8d4f-b87b57c5b330",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "from dotenv import load_dotenv\n",
    "from openai import OpenAI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d1715421-cead-400b-99af-986388a97aff",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gradio as gr # oh yeah!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "337d5dfc-0181-4e3b-8ab9-e78e0c3f657b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load environment variables in a file called .env\n",
    "# Print the key prefixes to help with any debugging\n",
    "# You can choose whichever providers you like - or all Ollama\n",
    "\n",
    "load_dotenv(override=True)\n",
    "openai_api_key = os.getenv('OPENAI_API_KEY')\n",
    "anthropic_api_key = os.getenv('ANTHROPIC_API_KEY')\n",
    "google_api_key = os.getenv('GOOGLE_API_KEY')\n",
    "\n",
    "if openai_api_key:\n",
    "    print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n",
    "else:\n",
    "    print(\"OpenAI API Key not set\")\n",
    "    \n",
    "if anthropic_api_key:\n",
    "    print(f\"Anthropic API Key exists and begins {anthropic_api_key[:7]}\")\n",
    "else:\n",
    "    print(\"Anthropic API Key not set\")\n",
    "\n",
    "if google_api_key:\n",
    "    print(f\"Google API Key exists and begins {google_api_key[:8]}\")\n",
    "else:\n",
    "    print(\"Google API Key not set\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22586021-1795-4929-8079-63f5bb4edd4c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Connect to OpenAI, Anthropic and Google; comment out the Claude or Google lines if you're not using them\n",
    "\n",
    "openai = OpenAI()\n",
    "\n",
    "anthropic_url = \"https://api.anthropic.com/v1/\"\n",
    "gemini_url = \"https://generativelanguage.googleapis.com/v1beta/openai/\"\n",
    "\n",
    "anthropic = OpenAI(api_key=anthropic_api_key, base_url=anthropic_url)\n",
    "gemini = OpenAI(api_key=google_api_key, base_url=gemini_url)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "02ef9b69-ef31-427d-86d0-b8c799e1c1b1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's wrap a call to GPT-4.1-mini in a simple function\n",
    "\n",
    "system_message = \"You are a helpful assistant\"\n",
    "\n",
    "def message_gpt(prompt):\n",
    "    messages = [{\"role\": \"system\", \"content\": system_message}, {\"role\": \"user\", \"content\": prompt}]\n",
    "    response = openai.chat.completions.create(model=\"gpt-4.1-mini\", messages=messages)\n",
    "    return response.choices[0].message.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aef7d314-2b13-436b-b02d-8de3b72b193f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# This can reveal the \"training cut off\", or the most recent date in the training data\n",
    "\n",
    "message_gpt(\"What is today's date?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f94013d1-4f27-4329-97e8-8c58db93636a",
   "metadata": {},
   "source": [
    "## User Interface time!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bc664b7a-c01d-4fea-a1de-ae22cdd5141a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# here's a simple function\n",
    "\n",
    "def shout(text):\n",
    "    print(f\"Shout has been called with input {text}\")\n",
    "    return text.upper()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "977bb496",
   "metadata": {},
   "outputs": [],
   "source": [
    "shout(\"hello\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "083ea451-d3a0-4d13-b599-93ed49b975e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "gr.Interface(fn=shout, inputs=\"textbox\", outputs=\"textbox\", flagging_mode=\"never\").launch()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "002ab4a6",
   "metadata": {},
   "source": [
    "<table style=\"margin: 0; text-align: left;\">\n",
    "    <tr>\n",
    "        <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
    "            <img src=\"../assets/important.jpg\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
    "        </td>\n",
    "        <td>\n",
    "            <h2 style=\"color:#900;\">NOTE: Using Gradio's Share tool</h2>\n",
    "            <span style=\"color:#900;\">I'm about to show you a really cool way to share your Gradio UI with others. This deploys your gradio app as a demo on gradio's website, and then allows gradio to call the 'shout' function. This uses an advanced technology known as 'HTTP tunneling' (like ngrok for people who know it) which isn't allowed by many Antivirus programs and corporate environments. If you get an error, just skip the next cell.<br/>\n",
    "            </span>\n",
    "        </td>\n",
    "    </tr>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c9a359a4-685c-4c99-891c-bb4d1cb7f426",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Adding share=True means that it can be accessed publically\n",
    "# A more permanent hosting is available using a platform called Spaces from HuggingFace, which we will touch on next week\n",
    "# NOTE: Some Anti-virus software and Corporate Firewalls might not like you using share=True. \n",
    "# If you're at work on on a work network, I suggest skip this test.\n",
    "\n",
    "gr.Interface(fn=shout, inputs=\"textbox\", outputs=\"textbox\", flagging_mode=\"never\").launch(share=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cd87533a-ff3a-4188-8998-5bedd5ba2da3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Adding inbrowser=True opens up a new browser window automatically\n",
    "\n",
    "gr.Interface(fn=shout, inputs=\"textbox\", outputs=\"textbox\", flagging_mode=\"never\").launch(inbrowser=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42945b17",
   "metadata": {},
   "source": [
    "## Adding authentication\n",
    "\n",
    "Gradio makes it very easy to have userids and passwords\n",
    "\n",
    "Obviously if you use this, have it look properly in a secure place for passwords! At a minimum, use your .env"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c34e6735",
   "metadata": {},
   "outputs": [],
   "source": [
    "gr.Interface(fn=shout, inputs=\"textbox\", outputs=\"textbox\", flagging_mode=\"never\").launch(inbrowser=True, auth=(\"ed\", \"bananas\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b42ec007-0314-48bf-84a4-a65943649215",
   "metadata": {},
   "source": [
    "## Forcing dark mode\n",
    "\n",
    "Gradio appears in light mode or dark mode depending on the settings of the browser and computer. There is a way to force gradio to appear in dark mode, but Gradio recommends against this as it should be a user preference (particularly for accessibility reasons). But if you wish to force dark mode for your screens, below is how to do it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e8129afa-532b-4b15-b93c-aa9cca23a546",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define this variable and then pass js=force_dark_mode when creating the Interface\n",
    "\n",
    "force_dark_mode = \"\"\"\n",
    "function refresh() {\n",
    "    const url = new URL(window.location);\n",
    "    if (url.searchParams.get('__theme') !== 'dark') {\n",
    "        url.searchParams.set('__theme', 'dark');\n",
    "        window.location.href = url.href;\n",
    "    }\n",
    "}\n",
    "\"\"\"\n",
    "gr.Interface(fn=shout, inputs=\"textbox\", outputs=\"textbox\", flagging_mode=\"never\", js=force_dark_mode).launch()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3cc67b26-dd5f-406d-88f6-2306ee2950c0",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Adding a little more:\n",
    "\n",
    "message_input = gr.Textbox(label=\"Your message:\", info=\"Enter a message to be shouted\", lines=7)\n",
    "message_output = gr.Textbox(label=\"Response:\", lines=8)\n",
    "\n",
    "view = gr.Interface(\n",
    "    fn=shout,\n",
    "    title=\"Shout\", \n",
    "    inputs=[message_input], \n",
    "    outputs=[message_output], \n",
    "    examples=[\"hello\", \"howdy\"], \n",
    "    flagging_mode=\"never\"\n",
    "    )\n",
    "view.launch()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f235288e-63a2-4341-935b-1441f9be969b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# And now - changing the function from \"shout\" to \"message_gpt\"\n",
    "\n",
    "message_input = gr.Textbox(label=\"Your message:\", info=\"Enter a message for GPT-4.1-mini\", lines=7)\n",
    "message_output = gr.Textbox(label=\"Response:\", lines=8)\n",
    "\n",
    "view = gr.Interface(\n",
    "    fn=message_gpt,\n",
    "    title=\"GPT\", \n",
    "    inputs=[message_input], \n",
    "    outputs=[message_output], \n",
    "    examples=[\"hello\", \"howdy\"], \n",
    "    flagging_mode=\"never\"\n",
    "    )\n",
    "view.launch()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af9a3262-e626-4e4b-80b0-aca152405e63",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's use Markdown\n",
    "# Are you wondering why it makes any difference to set system_message when it's not referred to in the code below it?\n",
    "# I'm taking advantage of system_message being a global variable, used back in the message_gpt function (go take a look)\n",
    "# Not a great software engineering practice, but quite common during Jupyter Lab R&D!\n",
    "\n",
    "system_message = \"You are a helpful assistant that responds in markdown without code blocks\"\n",
    "\n",
    "message_input = gr.Textbox(label=\"Your message:\", info=\"Enter a message for GPT-4.1-mini\", lines=7)\n",
    "message_output = gr.Markdown(label=\"Response:\")\n",
    "\n",
    "view = gr.Interface(\n",
    "    fn=message_gpt,\n",
    "    title=\"GPT\", \n",
    "    inputs=[message_input], \n",
    "    outputs=[message_output], \n",
    "    examples=[\n",
    "        \"Explain the Transformer architecture to a layperson\",\n",
    "        \"Explain the Transformer architecture to an aspiring AI engineer\",\n",
    "        ], \n",
    "    flagging_mode=\"never\"\n",
    "    )\n",
    "view.launch()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "88c04ebf-0671-4fea-95c9-bc1565d4bb4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's create a call that streams back results\n",
    "# If you'd like a refresher on Generators (the \"yield\" keyword),\n",
    "# Please take a look at the Intermediate Python guide in the guides folder\n",
    "\n",
    "def stream_gpt(prompt):\n",
    "    messages = [\n",
    "        {\"role\": \"system\", \"content\": system_message},\n",
    "        {\"role\": \"user\", \"content\": prompt}\n",
    "      ]\n",
    "    stream = openai.chat.completions.create(\n",
    "        model='gpt-4.1-mini',\n",
    "        messages=messages,\n",
    "        stream=True\n",
    "    )\n",
    "    result = \"\"\n",
    "    for chunk in stream:\n",
    "        result += chunk.choices[0].delta.content or \"\"\n",
    "        yield result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0bb1f789-ff11-4cba-ac67-11b815e29d09",
   "metadata": {},
   "outputs": [],
   "source": [
    "message_input = gr.Textbox(label=\"Your message:\", info=\"Enter a message for GPT-4.1-mini\", lines=7)\n",
    "message_output = gr.Markdown(label=\"Response:\")\n",
    "\n",
    "view = gr.Interface(\n",
    "    fn=stream_gpt,\n",
    "    title=\"GPT\", \n",
    "    inputs=[message_input], \n",
    "    outputs=[message_output], \n",
    "    examples=[\n",
    "        \"Explain the Transformer architecture to a layperson\",\n",
    "        \"Explain the Transformer architecture to an aspiring AI engineer\",\n",
    "        ], \n",
    "    flagging_mode=\"never\"\n",
    "    )\n",
    "view.launch()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bbc8e930-ba2a-4194-8f7c-044659150626",
   "metadata": {},
   "outputs": [],
   "source": [
    "def stream_claude(prompt):\n",
    "    messages = [\n",
    "        {\"role\": \"system\", \"content\": system_message},\n",
    "        {\"role\": \"user\", \"content\": prompt}\n",
    "      ]\n",
    "    stream = anthropic.chat.completions.create(\n",
    "        model='claude-sonnet-4-5-20250929',\n",
    "        messages=messages,\n",
    "        stream=True\n",
    "    )\n",
    "    result = \"\"\n",
    "    for chunk in stream:\n",
    "        result += chunk.choices[0].delta.content or \"\"\n",
    "        yield result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a0066ffd-196e-4eaf-ad1e-d492958b62af",
   "metadata": {},
   "outputs": [],
   "source": [
    "message_input = gr.Textbox(label=\"Your message:\", info=\"Enter a message for Claude 4.5 Sonnet\", lines=7)\n",
    "message_output = gr.Markdown(label=\"Response:\")\n",
    "\n",
    "view = gr.Interface(\n",
    "    fn=stream_claude,\n",
    "    title=\"Claude\", \n",
    "    inputs=[message_input], \n",
    "    outputs=[message_output], \n",
    "    examples=[\n",
    "        \"Explain the Transformer architecture to a layperson\",\n",
    "        \"Explain the Transformer architecture to an aspiring AI engineer\",\n",
    "        ], \n",
    "    flagging_mode=\"never\"\n",
    "    )\n",
    "view.launch()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc5a70b9-2afe-4a7c-9bed-2429229e021b",
   "metadata": {},
   "source": [
    "## And now getting fancy\n",
    "\n",
    "Remember to check the Intermediate Python Guide if you're unsure about generators and \"yield\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0087623a-4e31-470b-b2e6-d8d16fc7bcf5",
   "metadata": {},
   "outputs": [],
   "source": [
    "def stream_model(prompt, model):\n",
    "    if model==\"GPT\":\n",
    "        result = stream_gpt(prompt)\n",
    "    elif model==\"Claude\":\n",
    "        result = stream_claude(prompt)\n",
    "    else:\n",
    "        raise ValueError(\"Unknown model\")\n",
    "    yield from result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8d8ce810-997c-4b6a-bc4f-1fc847ac8855",
   "metadata": {},
   "outputs": [],
   "source": [
    "message_input = gr.Textbox(label=\"Your message:\", info=\"Enter a message for the LLM\", lines=7)\n",
    "model_selector = gr.Dropdown([\"GPT\", \"Claude\"], label=\"Select model\", value=\"GPT\")\n",
    "message_output = gr.Markdown(label=\"Response:\")\n",
    "\n",
    "view = gr.Interface(\n",
    "    fn=stream_model,\n",
    "    title=\"LLMs\", \n",
    "    inputs=[message_input, model_selector], \n",
    "    outputs=[message_output], \n",
    "    examples=[\n",
    "            [\"Explain the Transformer architecture to a layperson\", \"GPT\"],\n",
    "            [\"Explain the Transformer architecture to an aspiring AI engineer\", \"Claude\"]\n",
    "        ], \n",
    "    flagging_mode=\"never\"\n",
    "    )\n",
    "view.launch()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d933865b-654c-4b92-aa45-cf389f1eda3d",
   "metadata": {},
   "source": [
    "# Building a company brochure generator\n",
    "\n",
    "Now you know how - it's simple!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "92d7c49b-2e0e-45b3-92ce-93ca9f962ef4",
   "metadata": {},
   "source": [
    "<table style=\"margin: 0; text-align: left;\">\n",
    "    <tr>\n",
    "        <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
    "            <img src=\"../assets/important.jpg\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
    "        </td>\n",
    "        <td>\n",
    "            <h2 style=\"color:#900;\">Before you read the next few cells</h2>\n",
    "            <span style=\"color:#900;\">\n",
    "                Try to do this yourself - go back to the company brochure in week1, day5 and add a Gradio UI to the end. Then come and look at the solution.\n",
    "            </span>\n",
    "        </td>\n",
    "    </tr>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1626eb2e-eee8-4183-bda5-1591b58ae3cf",
   "metadata": {},
   "outputs": [],
   "source": [
    "from scraper import fetch_website_contents"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c701ec17-ecd5-4000-9f68-34634c8ed49d",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# Again this is typical Experimental mindset - I'm changing the global variable we used above:\n",
    "\n",
    "system_message = \"\"\"\n",
    "You are an assistant that analyzes the contents of a company website landing page\n",
    "and creates a short brochure about the company for prospective customers, investors and recruits.\n",
    "Respond in markdown without code blocks.\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5def90e0-4343-4f58-9d4a-0e36e445efa4",
   "metadata": {},
   "outputs": [],
   "source": [
    "def stream_brochure(company_name, url, model):\n",
    "    yield \"\"\n",
    "    prompt = f\"Please generate a company brochure for {company_name}. Here is their landing page:\\n\"\n",
    "    prompt += fetch_website_contents(url)\n",
    "    if model==\"GPT\":\n",
    "        result = stream_gpt(prompt)\n",
    "    elif model==\"Claude\":\n",
    "        result = stream_claude(prompt)\n",
    "    else:\n",
    "        raise ValueError(\"Unknown model\")\n",
    "    yield from result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "66399365-5d67-4984-9d47-93ed26c0bd3d",
   "metadata": {},
   "outputs": [],
   "source": [
    "name_input = gr.Textbox(label=\"Company name:\")\n",
    "url_input = gr.Textbox(label=\"Landing page URL including http:// or https://\")\n",
    "model_selector = gr.Dropdown([\"GPT\", \"Claude\"], label=\"Select model\", value=\"GPT\")\n",
    "message_output = gr.Markdown(label=\"Response:\")\n",
    "\n",
    "view = gr.Interface(\n",
    "    fn=stream_brochure,\n",
    "    title=\"Brochure Generator\", \n",
    "    inputs=[name_input, url_input, model_selector], \n",
    "    outputs=[message_output], \n",
    "    examples=[\n",
    "            [\"Hugging Face\", \"https://huggingface.co\", \"GPT\"],\n",
    "            [\"Edward Donner\", \"https://edwarddonner.com\", \"Claude\"]\n",
    "        ], \n",
    "    flagging_mode=\"never\"\n",
    "    )\n",
    "view.launch()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "611dd9c4",
   "metadata": {},
   "source": [
    "<table style=\"margin: 0; text-align: left;\">\n",
    "    <tr>\n",
    "        <td style=\"width: 150px; height: 150px; vertical-align: middle;\">\n",
    "            <img src=\"../assets/resources.jpg\" width=\"150\" height=\"150\" style=\"display: block;\" />\n",
    "        </td>\n",
    "        <td>\n",
    "            <h2 style=\"color:#f71;\">Gradio Resources</h2>\n",
    "            <span style=\"color:#f71;\">If you'd like to go deeper on Gradio, check out the amazing documentation - a wonderful rabbit hole.<br/>\n",
    "            <a href=\"https://www.gradio.app/guides/quickstart\">https://www.gradio.app/guides/quickstart</a><br/>Gradio is primarily designed for Demos, Prototypes and MVPs, but I've also used it frequently to make internal apps for power users.\n",
    "            </span>\n",
    "        </td>\n",
    "    </tr>\n",
    "</table>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
